home *** CD-ROM | disk | FTP | other *** search
/ Nejlepší hry / Nejlepsi hry.iso / hry / frozen bubble / fbinstaller.exe / {app} / lib / FBLE.pm < prev    next >
Encoding:
Perl POD Document  |  2003-04-16  |  65.6 KB  |  1,751 lines

  1. # ****************************************************************************
  2. #
  3. #                          Frozen-Bubble Level Editor
  4. #
  5. # Copyright (c) 2002 - 2003 Kim Joham and David Joham <[k|d]joham@yahoo.com>
  6. #
  7. # This program is free software; you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License version 2, as
  9. # published by the Free Software Foundation.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with this program; if not, write to the Free Software
  18. # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  19. #
  20. #
  21. # *****************************************************************************
  22. #
  23. # Design & Programming by Kim Joham and David Joham, October 2002 - January 2003
  24. #
  25. # pencil icon taken from the GIMP project
  26. #
  27. #
  28. # Integration to Frozen-Bubble by Guillaume Cottenceau - change a few styles
  29. # things, fix a few bugs, add a few features
  30. #
  31. #
  32. # *****************************************************************************
  33.  
  34. package FBLE;
  35.  
  36. use POSIX;
  37. use SDL;
  38. use SDL::App;
  39. use SDL::Surface;
  40. use SDL::Event;
  41. use SDL::Cursor;
  42. use SDL::Font;
  43. use SDL::Mixer;
  44.  
  45. use fb_stuff;
  46. use fbsyms;
  47.  
  48. use strict;
  49. our ($NUM_ROWS, $NUM_BUBBLES_AVAIL,
  50.      $BUBBLES_PER_ROW, $BUBBLE_OPTION_SEPARATION, $BUBBLE_OPTION_INIT_X,
  51.      $ALPHA_BUBBLE_NO, $ALPHA_BUBBLE_YES, $WOOD_WIDTH, $WOOD_PLANK_HEIGHT,
  52.      $LEFT_WOOD_X, $RIGHT_WOOD_X, $BUBBLE_WOOD_Y, $NAV_WOOD_Y, $LEVELSET_WOOD_Y,
  53.      $LEVEL_WOOD_Y, $colourblind, $app, $font, %bubble_rects, %bubble_hash, $color,
  54.      $action, $previousx, $previousy, $background, $highlight,
  55.      $highlighted_option, $curr_level, $displaying_dialog, $new_ls_name_text, $surface_dialog,
  56.      $levelset_name, $list_browser_file_start_offset, $list_browser_highlight_offset,
  57.      $surf_shrink, @file_browser_screenshots, $deleted_current_levelset, 
  58.      $modified_levelset, $modified_levelset_action, $button_hold, $command_line_fullscreen, %rect);
  59.  
  60.  
  61. $NUM_ROWS = 10;
  62. $POS_1P{bottom_limit} = $POS_1P{init_top_limit} + $NUM_ROWS * $ROW_SIZE;
  63. $NUM_BUBBLES_AVAIL = 8;
  64. $BUBBLES_PER_ROW = 3;
  65. $BUBBLE_OPTION_SEPARATION = 8;
  66. $BUBBLE_OPTION_INIT_X = 18;
  67. $ALPHA_BUBBLE_NO = 0;
  68. $ALPHA_BUBBLE_YES = 1;
  69.  
  70. $WOOD_WIDTH = 150;
  71. $WOOD_PLANK_HEIGHT = 40;
  72. $LEFT_WOOD_X = 2;
  73. $RIGHT_WOOD_X = 488;
  74. $BUBBLE_WOOD_Y = 33;
  75. $NAV_WOOD_Y = 209;
  76. $LEVELSET_WOOD_Y = 30;
  77. $LEVEL_WOOD_Y = 249;
  78.  
  79. $highlighted_option = '';
  80. $previousx = -1;
  81. $previousy = -1;
  82. $displaying_dialog = '';
  83. $deleted_current_levelset = 0;
  84. $button_hold = 0;
  85.  
  86.  
  87. #- ----------- bubbles processing/drawing -----------------------------------------
  88.  
  89. # subroutine to calculate the left corner x of the given bubble option column (based on 0 start)
  90. sub bubble_optionx {
  91.     my ($col) = @_;
  92.     return $BUBBLE_OPTION_INIT_X + $col * ($BUBBLE_SIZE + $BUBBLE_OPTION_SEPARATION);
  93. }
  94.  
  95. # subroutine to calculate the left corner y of the given bubble option row (based on 0 start)
  96. sub bubble_optiony {
  97.     my ($row) = @_;
  98.     return $BUBBLE_WOOD_Y + $WOOD_PLANK_HEIGHT + $row * ($BUBBLE_SIZE + $BUBBLE_OPTION_SEPARATION);
  99. }
  100.  
  101. # subroutine to get the column
  102. sub get_col {
  103.     my ($x, $y) = @_;
  104.     if (even(get_row($y))) {
  105.         return floor(($x-$POS_1P{p1}{left_limit})/$BUBBLE_SIZE);
  106.  
  107.     } elsif ($POS_1P{p1}{left_limit} + $BUBBLE_SIZE/2 <= $x && $x < $POS_1P{p1}{right_limit} - $BUBBLE_SIZE/2)  { 
  108.         return floor(($x-($POS_1P{p1}{left_limit}+$BUBBLE_SIZE/2))/$BUBBLE_SIZE);
  109.  
  110.     } else {
  111.         return -1;
  112.     }
  113. }
  114.  
  115. # subroutine to get the row
  116. sub get_row {
  117.     my ($y) = @_;
  118.     return floor(($y-$POS_1P{init_top_limit})/$ROW_SIZE);
  119. }
  120.  
  121. # subroutine to draw bubbles
  122. sub draw_bubble {
  123.     my ($bubbleid, $x, $y, $alpha, $surface_tmp, $ignore_update) = @_;
  124.     my ($bubble);
  125.  
  126.     $surface_tmp or $surface_tmp = $app;
  127.     $bubble = SDL::Surface->new(-name => "$FPATH/gfx/balls/bubble-".($colourblind && 'colourblind-')."$bubbleid.gif");
  128.  
  129.     $bubble_rects{$x}{$y} = SDL::Rect->new(-x => $x, '-y' => $y, -width => $bubble->width, -height => $bubble->height);
  130.  
  131.     $alpha and $bubble->set_alpha(SDL_SRCALPHA, 0x66);
  132.  
  133.     $bubble->blit(NULL, $surface_tmp, $bubble_rects{$x}{$y});
  134.     $ignore_update or $surface_tmp->update($bubble_rects{$x}{$y});
  135. }
  136.  
  137. # subroutine to erase bubble
  138. sub erase_bubble {
  139.     my ($x, $y) = @_;
  140.     $background->blit($bubble_rects{$x}{$y}, $app, $bubble_rects{$x}{$y});
  141.     #- redraw close bubbles because the rectangular blit of the previous statement erased a bit of them 
  142.     my $DISTANCE_CLOSE_SQRED = sqr($BUBBLE_SIZE*1.1);
  143.     foreach my $x_ (keys %bubble_rects) {
  144.     foreach my $y_ (%{$bubble_rects{$x_}}) {
  145.         $y != $y_ && sqr($x-$x_) + sqr($y-$y_) <= $DISTANCE_CLOSE_SQRED or next;
  146.         if ($bubble_hash{$curr_level}{my $col = get_col($x_, $y_)}{my $row = get_row($y_)} =~ /^\d+$/) {
  147.         draw_bubble($bubble_hash{$curr_level}{$col}{$row} + 1, $x_, $y_, $ALPHA_BUBBLE_NO, undef, 1);
  148.         }
  149.     }
  150.     }
  151.     $app->update($bubble_rects{$x}{$y});
  152. }
  153.  
  154. # subroutine to place a bubble
  155. sub place_bubble {
  156.     my ($x, $y, $alpha, $button) = @_;
  157.     my $col = get_col($x, $y);
  158.     my $row = get_row($y);
  159.     $y = $row * $ROW_SIZE + $POS_1P{init_top_limit};
  160.     if ($col != -1) {
  161.         if (even($row)) {
  162.             $x = get_col($x, $y) * $BUBBLE_SIZE + $POS_1P{p1}{left_limit};
  163.         } else  {
  164.             $x = $col * $BUBBLE_SIZE + $POS_1P{p1}{left_limit} + $BUBBLE_SIZE/2;
  165.         }
  166.     if ($action eq 'erase' || $button >= 3) {  #- when in motion, the right button is reported as button 4 !?
  167.             if (($previousx != $x || $previousy != $y) && $previousx != -1 && $previousy != -1
  168.         && $bubble_hash{$curr_level}{get_col($previousx, $previousy)}{get_row($previousy)} ne '-') {
  169.                 draw_bubble($bubble_hash{$curr_level}{get_col($previousx, $previousy)}{get_row($previousy)} + 1,
  170.                 $previousx, $previousy, $ALPHA_BUBBLE_NO);
  171.             }
  172.         if ($bubble_rects{$x}{$y}) {
  173.         if ($alpha && $bubble_hash{$curr_level}{$col}{$row} ne '-') {
  174.             if ($previousx != $x || $previousy != $y) {
  175.             erase_bubble($x, $y);
  176.             draw_bubble($bubble_hash{$curr_level}{$col}{$row} + 1, $x, $y, $alpha);
  177.             }
  178.             $previousx = $x;
  179.             $previousy = $y;
  180.         } else {
  181.             $bubble_hash{$curr_level}{$col}{$row} = '-';
  182.             erase_bubble($x, $y);
  183.         }
  184.             }
  185.         } elsif ($action eq 'add') {
  186.             if ($alpha) {
  187.                 if ($previousx != $x || $previousy != $y) {
  188.                     if ($previousx != -1 && $previousy != -1) {
  189.                         if ($bubble_hash{$curr_level}{get_col($previousx, $previousy)}{get_row($previousy)} eq '-') {
  190.                             erase_bubble($previousx, $previousy);
  191.                         } else {
  192.                             draw_bubble($bubble_hash{$curr_level}{get_col($previousx, $previousy)}{get_row($previousy)} + 1,
  193.                     $previousx, $previousy, $ALPHA_BUBBLE_NO);
  194.                         }
  195.                     }
  196.                     draw_bubble($color, $x, $y, $alpha);
  197.                     $previousx = $x;
  198.                     $previousy = $y;
  199.                 }
  200.             } else {
  201.                 $bubble_hash{$curr_level}{$col}{$row} = $color - 1;
  202.                 draw_bubble($color, $x, $y, $alpha);
  203.             }
  204.     }
  205.     }
  206. }
  207.  
  208.  
  209. #- ----------- actions routing -----------------------------------------
  210.  
  211. # subroutine to change my color
  212. sub change_color {
  213.     my ($new_color) = @_;
  214.     $color = $new_color;
  215.     $action = 'add';
  216.  
  217.     draw_bubble($color,
  218.         $POS_1P{next_bubble}{x} + $POS_1P{p1}{left_limit},
  219.         $POS_1P{next_bubble}{y},$ALPHA_BUBBLE_NO);    #- }}})
  220. }
  221.  
  222. sub highlight_option {
  223.     my ($option, $x, $y) = @_;
  224.  
  225.     unhighlight_option($option);
  226.     if ($option ne $highlighted_option) {
  227.         $highlighted_option = $option;
  228.         $option =~ s/bubble-(\d+)/$1/;
  229.  
  230.         if (0 < $option && $option <= $NUM_BUBBLES_AVAIL) {
  231.             $highlight->blit($rect{bubble_option_highlight}, $app, $bubble_rects{$x}{$y});
  232.             $app->update($bubble_rects{$x}{$y});
  233.  
  234.         } elsif ($option eq 'erase') {
  235.             $highlight->blit($rect{bubble_option_highlight}, $app, $rect{erase});
  236.             $app->update($rect{erase});
  237.  
  238.         } elsif ($option =~ m/arrow/){
  239.             eval "print_dialog_$option(1)";
  240.  
  241.         } else {
  242.             eval "print_$option".'_text(1)';
  243.         }
  244.     }
  245.  
  246. }
  247.  
  248. sub unhighlight_option {
  249.     # note: will only have x and y for bubbles and erase
  250.     my ($no_highlight) = @_;
  251.     my ($col, $row);
  252.  
  253.     # don't unhighlight currently highlighted option because it will cause flashing
  254.     if ($highlighted_option ne '' && $highlighted_option ne $no_highlight) {
  255.         $highlighted_option =~ s/bubble-(\d+)/$1/;
  256.         if (0 < $highlighted_option && $highlighted_option <= $NUM_BUBBLES_AVAIL) {
  257.             $col = ($highlighted_option - 1 ) % $BUBBLES_PER_ROW;
  258.             $row = floor(($highlighted_option - 1 ) / $BUBBLES_PER_ROW);
  259.  
  260.         my $rect = $bubble_rects{bubble_optionx($col)}{bubble_optiony($row)};
  261.             $background->blit($rect, $app, $rect);
  262.             $app->update($rect);
  263.             
  264.             add_bubble_option($highlighted_option, $col, $row);
  265.     
  266.         } elsif ($highlighted_option eq 'erase') {
  267.             $background->blit($rect{erase}, $app, $rect{erase});
  268.             $app->update($rect{erase});
  269.             
  270.             add_erase_option();
  271.                                   
  272.         } elsif ($highlighted_option =~ m/arrow/) {
  273.             eval "print_dialog_$highlighted_option(0)";
  274.  
  275.         } else {
  276.             eval "print_$highlighted_option".'_text(0)';
  277.         }
  278.  
  279.         $highlighted_option = '';
  280.     }
  281.  
  282. }
  283.  
  284. # subroutine to decide what my mouse is telling me to do
  285. sub choose_action {
  286.     my ($x, $y, $caller, $button) = @_;
  287.  
  288.     # are we over the drawing area?
  289.     if ($POS_1P{p1}{left_limit} <= $x && $x < $POS_1P{p1}{right_limit} && $POS_1P{init_top_limit} <= $y && $y < $POS_1P{bottom_limit}) {
  290.         if ($caller eq 'motion' && $button_hold == 0) {
  291.             place_bubble($x, $y, $ALPHA_BUBBLE_YES, $button);
  292.         } elsif ($button_hold == 1) {
  293.             place_bubble($x, $y, $ALPHA_BUBBLE_NO, $button);
  294.             $modified_levelset = 1;
  295.         } else { # $caller is 'button'
  296.             place_bubble($x, $y, $ALPHA_BUBBLE_NO, $button);
  297.             $modified_levelset = 1;
  298.         }
  299.  
  300.     # we will want to remove bubble highlight if we go out of our drawing area
  301.     } elsif ($previousx != -1 && $previousy != -1) {
  302.         if ($bubble_hash{$curr_level}{get_col($previousx, $previousy)}{get_row($previousy)} eq '-') {
  303.             erase_bubble($previousx, $previousy);
  304.         } else {
  305.             draw_bubble($bubble_hash{$curr_level}{get_col($previousx, $previousy)}{get_row($previousy)} + 1,
  306.             $previousx, $previousy, $ALPHA_BUBBLE_NO);
  307.         }
  308.         # make sure that when we come back into the drawing area, we immediatly start
  309.         # doing our highlights - if we don't do this, you don't get a highlight
  310.         # if you come back to the same spot that you left
  311.         $previousx = -1;
  312.         $previousy = -1;
  313.     }
  314.    
  315.     # selecting a bubble or erase bubble??
  316.     if ($y >= $BUBBLE_WOOD_Y + $WOOD_PLANK_HEIGHT && $y <= $BUBBLE_WOOD_Y + 4 * $WOOD_PLANK_HEIGHT
  317.     && $x > $BUBBLE_OPTION_INIT_X - $BUBBLE_OPTION_SEPARATION/2
  318.     && $x <= $BUBBLE_OPTION_INIT_X + $BUBBLES_PER_ROW * ($BUBBLE_SIZE + $BUBBLE_OPTION_SEPARATION) - $BUBBLE_OPTION_SEPARATION/2) {
  319.         my $col = ceil(($x - $BUBBLE_OPTION_INIT_X + $BUBBLE_OPTION_SEPARATION/2) / ($BUBBLE_SIZE + $BUBBLE_OPTION_SEPARATION));
  320.         my $row = ceil(($y - ($BUBBLE_WOOD_Y + $WOOD_PLANK_HEIGHT) + $BUBBLE_OPTION_SEPARATION/2) / ($BUBBLE_SIZE + $BUBBLE_OPTION_SEPARATION));
  321.         
  322.         my $color_tmp = $BUBBLES_PER_ROW * ($row - 1) + $col;
  323.         if (0 < $color_tmp && $color_tmp <= $NUM_BUBBLES_AVAIL) {
  324.             highlight_option("bubble-$color_tmp", bubble_optionx($col - 1), bubble_optiony($row - 1));
  325.             $caller eq 'button' and change_color($color_tmp);
  326.         } elsif ($color_tmp == $NUM_BUBBLES_AVAIL + 1) {
  327.             highlight_option('erase');
  328.             if ($caller eq 'button') {
  329.                 $action = 'erase';
  330.                 erase_bubble($POS_1P{next_bubble}{x} + $POS_1P{p1}{left_limit}, $POS_1P{next_bubble}{'y'});
  331.             }
  332.         }
  333.  
  334.     # check if over navigation options
  335.     } elsif ($LEFT_WOOD_X <= $x && $x <= $WOOD_WIDTH && $rect{prev}->y <= $y && $y <= $rect{last}->y + $rect{last}->height) {
  336.     my @nav_options = ({ name => 'prev',
  337.                  unhighlight => $curr_level == 1,
  338.                  action => sub { if ($curr_level != 1) { 
  339.                                  prev_level();
  340.                          $curr_level == 1 and unhighlight_option();
  341.                          } } },
  342.                { name => 'next',
  343.                  unhighlight => $curr_level == keys %bubble_hash,
  344.                  action => sub { if ($curr_level != keys %bubble_hash) {
  345.                                  next_level();
  346.                          $curr_level == keys %bubble_hash and unhighlight_option();
  347.                          } } },
  348.                { name => 'first',
  349.                  unhighlight => $curr_level == 1,
  350.                  action => sub { if ($curr_level != 1) {
  351.                                  first_level();
  352.                          unhighlight_option();
  353.                          } } },
  354.                { name => 'last',
  355.                  unhighlight => $curr_level == keys %bubble_hash,
  356.                  action => sub { if ($curr_level != keys %bubble_hash) {
  357.                                  last_level();
  358.                          unhighlight_option();
  359.                          } } },
  360.                );
  361.  
  362.     foreach (@nav_options) {
  363.         if ($rect{$_->{name}}->y <= $y && $y <= $rect{$_->{name}}->y + $rect{$_->{name}}->height) {
  364.         if ($_->{unhighlight}) {
  365.             unhighlight_option();
  366.         } else {
  367.             highlight_option($_->{name});
  368.         }
  369.         $caller eq 'button' and $_->{action}->();
  370.         }
  371.     }
  372.  
  373.     # check if over levelset options
  374.     } elsif ($RIGHT_WOOD_X <= $x && $x <= $RIGHT_WOOD_X + $WOOD_WIDTH
  375.          && $y >= $rect{ls_new}->y && $y <= $rect{ls_delete}->y + $rect{ls_delete}->height) {
  376.     my @ls_options = ({ name => 'ls_new',    action => sub { create_new_levelset_dialog() } },
  377.               { name => 'ls_open',   action => sub { create_open_levelset_dialog() } },
  378.               { name => 'ls_save',   action => sub { save_file() } },
  379.               { name => 'ls_delete', action => sub { create_delete_levelset_dialog() } });
  380.     foreach (@ls_options) {
  381.         if ($y >= $rect{$_->{name}}->y && $y <= $rect{$_->{name}}->y + $rect{$_->{name}}->height) {
  382.         highlight_option($_->{name});
  383.         $caller eq 'button' and $_->{action}->();
  384.         }
  385.     }
  386.  
  387.     # check if over level options
  388.     } elsif ($RIGHT_WOOD_X <= $x && $x <= $RIGHT_WOOD_X + $WOOD_WIDTH
  389.          && $y >= $rect{lvl_insert}->y && $y <= $rect{lvl_delete}->y + $rect{lvl_delete}->height) {
  390.     my @lvl_options = ({ name => 'lvl_insert', action => sub { insert_level() } },
  391.                { name => 'lvl_append', action => sub { append_level() } },
  392.                { name => 'lvl_delete', action => sub { delete_level(); load_level() } });
  393.     foreach (@lvl_options) {
  394.         if ($y >= $rect{$_->{name}}->y && $y <= $rect{$_->{name}}->y + $rect{$_->{name}}->height) {
  395.         highlight_option($_->{name});
  396.         $caller eq 'button' and $_->{action}->();
  397.         }
  398.     }
  399.  
  400.     # not over an option so I may need to unhighlight
  401.     } else {
  402.         unhighlight_option();
  403.     }
  404.  
  405. }
  406.  
  407. # subroutine to return the list of levelsets in $FBLEVELS
  408. sub get_levelset_list {
  409.     my @levelsets = map { $_ =~ s{^.*/(.*)$}{$1}; $_; } sort(glob("$FBLEVELS/*"));
  410.     $displaying_dialog eq 'ls_delete' and @levelsets = difference2(\@levelsets,['default-levelset']);
  411.     return @levelsets;
  412. }
  413.  
  414. sub betw {
  415.     my ($val, $min, $max) = @_;
  416.     $val > $min && $val < $max;
  417. }
  418.  
  419. # subroutine to decide what my mouse is telling me to do in the dialog box
  420. sub choose_dialog_action {
  421.     my ($x, $y, $caller) = @_;
  422.     my ($ok_rect, $cancel_rect, $surface_tmp);
  423.     
  424.     # todo - can we get this info somewhere else in a better way?
  425.     $surface_tmp = SDL::Surface->new(-name => "$FPATH/gfx/list_arrow_up.png");
  426.  
  427.     $rect{middle} = get_dialog_rect();
  428.     # over left button
  429.     if (betw($x, $rect{middle}->x, $rect{middle}->x + $rect{middle}->width/2)
  430.     && betw($y, $rect{middle}->y + 6 * $WOOD_PLANK_HEIGHT, $rect{middle}->y + $rect{middle}->height)) {
  431.     if (member($displaying_dialog, qw(ls_play ls_nothing_to_delete ls_open_ok_only ls_new_ok_only))) {
  432.         # this dialog does not have a left button. return
  433.         unhighlight_option();
  434.         return 1;
  435.     }
  436.  
  437.     if ($displaying_dialog eq 'ls_new' && is_ok_filename() == 1) {
  438.         highlight_option('ok');
  439.     } else {
  440.         unhighlight_option('ok');
  441.     }
  442.     
  443.     $displaying_dialog ne 'ls_new' and highlight_option('ok');
  444.     
  445.     if ($caller eq 'button') {
  446.         if ($displaying_dialog eq 'ls_new' && is_ok_filename() == 1) {
  447.         remove_dialog();
  448.         create_new_levelset();
  449.         } elsif ($displaying_dialog eq 'ls_open') {
  450.         open_levelset();
  451.         } elsif ($displaying_dialog eq 'ls_delete') {
  452.         delete_levelset();
  453.         } elsif ($displaying_dialog eq 'ls_deleted_current') {
  454.         create_open_levelset_dialog_ok_only();
  455.         } elsif ($displaying_dialog eq 'ls_save_changes') {
  456.         save_file();
  457.         $displaying_dialog = '';
  458.         eval($modified_levelset_action);
  459.         }
  460.     }
  461.  
  462.     # over right button
  463.     } elsif (betw($x, $rect{middle}->x + $rect{middle}->width/2, $rect{middle}->x + $rect{middle}->width)
  464.          && betw($y, $rect{middle}->y + 6 * $WOOD_PLANK_HEIGHT, $rect{middle}->y + $rect{middle}->height)) {
  465.     if (member($displaying_dialog, qw(ls_play ls_nothing_to_delete ls_open_ok_only ls_new_ok_only))) {
  466.             if ($displaying_dialog eq 'ls_new_ok_only' && is_ok_filename() == 1) {
  467.         highlight_option('ok_right');
  468.             } else {
  469.                 unhighlight_option('ok_right');
  470.             }
  471.  
  472.             if ($displaying_dialog eq 'ls_nothing_to_delete') {
  473.                 highlight_option('ok_right');
  474.                 if ($caller eq 'button') {
  475.                     $displaying_dialog = '';
  476.                     remove_dialog();
  477.                 }
  478.             }
  479.  
  480.             $displaying_dialog ne 'ls_new_ok_only' and highlight_option('ok_right');
  481.  
  482.         if ($caller eq 'button') {
  483.         if ($displaying_dialog eq 'ls_play') {
  484.             my @levelsets = get_levelset_list();
  485.             $modified_levelset_action = "return $levelsets[$list_browser_highlight_offset]";
  486.             return 1;
  487.  
  488.         } elsif ($displaying_dialog eq 'ls_open_ok_only') {
  489.             remove_dialog();
  490.                     $displaying_dialog = '';
  491.                     open_levelset();
  492.  
  493.         } elsif ($displaying_dialog eq 'ls_new_ok_only' && is_ok_filename() == 1) {
  494.                     remove_dialog();
  495.                     create_new_levelset();
  496.                 }
  497.         }
  498.     } else {
  499.         highlight_option('cancel');
  500.         if ($caller eq 'button') {
  501.         if ($displaying_dialog eq 'ls_open' && $deleted_current_levelset == 1) {
  502.             remove_dialog();
  503.             create_deleted_current_levelset_dialog();
  504.  
  505.         } elsif ($displaying_dialog eq 'ls_deleted_current') {
  506.             $levelset_name = 'default-levelset';
  507.             %bubble_hash = read_file($levelset_name);
  508.             $curr_level = 1;
  509.                     $displaying_dialog = '';
  510.                     remove_dialog();
  511.                     load_level();
  512.                     print_levelset_name();
  513.                     $deleted_current_levelset = 0;
  514.  
  515.         } elsif ($displaying_dialog eq 'ls_save_changes') {
  516.                     $displaying_dialog = '';
  517.             eval($modified_levelset_action);
  518.  
  519.         } else {
  520.             remove_dialog();
  521.         }
  522.         }
  523.     }
  524.  
  525.     } elsif (member($displaying_dialog, qw(ls_open_ok_only ls_open ls_delete ls_play))) {
  526.     if (betw($x, $rect{middle}->x + 4 * $rect{middle}->width/6, $rect{middle}->x + 4 * $rect{middle}->width/6 + $surface_tmp->width)) {
  527.         my @arrows = ($rect{dialog_file_list}->y + 2,
  528.               $rect{dialog_file_list}->y + $rect{dialog_file_list}->height - $surface_tmp->height - 2);
  529.         if (betw($y, $arrows[0], $arrows[0] + $surface_tmp->height)) {
  530.         $caller eq 'button' and display_levelset_list_browser($list_browser_file_start_offset - 1, $list_browser_highlight_offset);
  531.         highlight_option('list_arrow_up');
  532.  
  533.         } elsif (betw($y, $arrows[1], $arrows[1] + $surface_tmp->height)) {
  534.         $caller eq 'button' and display_levelset_list_browser($list_browser_file_start_offset + 1, $list_browser_highlight_offset);
  535.         highlight_option('list_arrow_down');
  536.  
  537.         } else {
  538.         unhighlight_option();
  539.         }
  540.     } elsif (betw($x, $rect{dialog_file_list}->x, $rect{dialog_file_list}->x + $rect{dialog_file_list}->width)
  541.          && betw($y, $rect{dialog_file_list}->y, $rect{dialog_file_list}->y + $rect{dialog_file_list}->height)) {
  542.         if ($caller eq 'button') {
  543.         if ($y < $rect{dialog_file_list}->y + 25) {
  544.             display_levelset_list_browser($list_browser_file_start_offset, $list_browser_file_start_offset);
  545.         } elsif ($y < $rect{dialog_file_list}->y + 2 * 25) {
  546.             display_levelset_list_browser($list_browser_file_start_offset, $list_browser_file_start_offset + 1);
  547.         } elsif ($y < $rect{dialog_file_list}->y + 3 * 25) {
  548.             display_levelset_list_browser($list_browser_file_start_offset, $list_browser_file_start_offset + 2);
  549.         } else {
  550.             display_levelset_list_browser($list_browser_file_start_offset, $list_browser_file_start_offset + 3);
  551.         }
  552.         }
  553.     } else {
  554.         unhighlight_option();
  555.     }
  556.     } else {
  557.     unhighlight_option();
  558.     }
  559. }
  560.  
  561. # react to user's keyboard and mouse events
  562. sub handle_events {
  563.     my $event = SDL::Event->new;
  564.  
  565.     while (1) {
  566.         $event->pump;
  567.         if ($event->poll != 0) {
  568.     
  569.             if ($event->type == SDL_MOUSEMOTION) {
  570.                 if ($displaying_dialog eq '') {
  571.                     choose_action($event->button_x, $event->button_y, 'motion', $event->button);  #- , )
  572.                 } else {
  573.                     choose_dialog_action($event->button_x, $event->button_y, 'motion');  #- ,, )
  574.                 }
  575.                 $app->flip;
  576.  
  577.             } elsif ($event->type == SDL_MOUSEBUTTONDOWN) {
  578.                 $button_hold = 1;
  579.                 if ($displaying_dialog eq '') {
  580.                     choose_action($event->button_x, $event->button_y, 'button', $event->button);  #- , )
  581.                 } else {
  582.                     choose_dialog_action($event->button_x, $event->button_y, 'button');  #- ,, )
  583.                 }
  584.                 $app->flip;
  585.  
  586.             } elsif ($event->type == SDL_MOUSEBUTTONUP) {
  587.                 $button_hold = 0;
  588.  
  589.             } elsif ($event->type == SDL_KEYDOWN) {
  590.                 if ($displaying_dialog eq '') {
  591.                     if ($event->key_sym == SDLK_ESCAPE() || $event->key_sym == SDLK_q() ) {
  592.                         if ($modified_levelset == 1) {
  593.                             $modified_levelset_action = '$modified_levelset_action = "return 1"';
  594.                             create_save_changes_dialog();
  595.                         } else {
  596.                             return 1;
  597.                         }
  598.                     }
  599.                     $event->key_sym == SDLK_LEFT() and prev_level();
  600.                     $event->key_sym == SDLK_RIGHT() and next_level();
  601.                     $event->key_sym == SDLK_UP() and first_level();
  602.                     $event->key_sym == SDLK_DOWN() and last_level();
  603.                     $event->key_sym == SDLK_a() and append_level();
  604.                     $event->key_sym == SDLK_d() and do { delete_level(); FBLE::load_level() };
  605.                     $event->key_sym == SDLK_f() and $app->fullscreen;
  606.                     $event->key_sym == SDLK_h() and prev_level();
  607.                     $event->key_sym == SDLK_i() and insert_level();
  608.                     $event->key_sym == SDLK_l() and next_level();
  609.                     $event->key_sym == SDLK_n() and next_level();
  610.                     $event->key_sym == SDLK_o() and create_open_levelset_dialog();
  611.                     $event->key_sym == SDLK_p() and prev_level();
  612.                     $event->key_sym == SDLK_s() and save_file();
  613.                     $event->key_sym == SDLK_RIGHTBRACKET() and move_level_right();
  614.                     $event->key_sym == SDLK_LEFTBRACKET() and move_level_left();
  615.             $event->key_sym == SDLK_j() and do {
  616.             my $number;
  617.             while (1) {
  618.                 $event->pump;
  619.                 if ($event->poll != 0) {
  620.                 if ($event->type == SDL_KEYDOWN) {
  621.                     my $k = $event->key_sym;
  622.                     if ($k >= SDLK_0() && $k <= SDLK_9()) {
  623.                     $number .= $k-SDLK_0();
  624.                     }
  625.                     $k == SDLK_ESCAPE() and goto jump_end;
  626.                     $k == SDLK_RETURN() || $k == SDLK_KP_ENTER and goto jump_ok;
  627.                 }
  628.                 }
  629.                 $app->delay(1);
  630.             }
  631.               jump_ok:
  632.             jump_to_level($number);
  633.               jump_end:
  634.             };
  635.             if ((($highlighted_option eq 'prev' || $highlighted_option eq 'first') && $curr_level == 1)
  636.             || (($highlighted_option eq 'next' || $highlighted_option eq 'last') && $curr_level == keys %bubble_hash)) {
  637.             unhighlight_option();
  638.             }
  639.                 } elsif (member($displaying_dialog, qw(ls_new ls_new_ok_only))) {
  640.                     print_new_ls_name($event->key_sym);
  641.                 } elsif ($displaying_dialog eq 'ls_open') {
  642.                     if ($event->key_sym() == SDLK_RETURN() || $event->key_sym() == SDLK_KP_ENTER()) {
  643.                         highlight_option('ok');
  644.                         $app->delay(200);
  645.                         open_levelset();
  646.                     } elsif ($event->key_sym() == SDLK_ESCAPE()) {
  647.                         highlight_option('cancel');
  648.                         $app->delay(200);
  649.                         remove_dialog();
  650.                         if ($deleted_current_levelset == 1) {
  651.                             create_deleted_current_levelset_dialog();
  652.                         }
  653.                     } elsif ($event->key_sym() == SDLK_DOWN()) {
  654.                         display_levelset_list_browser($FBLE::list_browser_file_start_offset + 1, $FBLE::list_browser_highlight_offset + 1);
  655.                     } elsif ($event->key_sym() == SDLK_UP()) {
  656.                         display_levelset_list_browser($FBLE::list_browser_file_start_offset - 1, $FBLE::list_browser_highlight_offset - 1);
  657.             }
  658.                 } elsif ($displaying_dialog eq 'ls_open_ok_only') {
  659.                     if ($event->key_sym() == SDLK_RETURN() || $event->key_sym() == SDLK_KP_ENTER()) {
  660.                         highlight_option('ok_right');
  661.                         $app->delay(200);
  662.                         open_levelset();
  663.                     } elsif ($event->key_sym() == SDLK_DOWN()) {
  664.                         display_levelset_list_browser($FBLE::list_browser_file_start_offset + 1, $FBLE::list_browser_highlight_offset + 1);
  665.                     } elsif ($event->key_sym() == SDLK_UP()) {
  666.                         display_levelset_list_browser($FBLE::list_browser_file_start_offset - 1, $FBLE::list_browser_highlight_offset - 1);
  667.                     }
  668.                 } elsif ($displaying_dialog eq 'ls_play') {
  669.                     if ($event->key_sym == SDLK_RETURN() || $event->key_sym == SDLK_KP_ENTER()) {
  670.                         highlight_option('ok_right');
  671.                         $app->delay(200);
  672.                         my (@levelsets);
  673.                         @levelsets = get_levelset_list();
  674.                         $displaying_dialog = '';
  675.                         return $levelsets[$list_browser_highlight_offset];
  676.                     } elsif ($event->key_sym() == SDLK_DOWN()) {
  677.                         display_levelset_list_browser($FBLE::list_browser_file_start_offset + 1, $FBLE::list_browser_highlight_offset + 1);
  678.                     } elsif ($event->key_sym() == SDLK_UP()) {
  679.                         display_levelset_list_browser($FBLE::list_browser_file_start_offset - 1, $FBLE::list_browser_highlight_offset - 1);
  680.                     }
  681.                 } elsif ($displaying_dialog eq 'ls_delete') {
  682.                     if ($event->key_sym() == SDLK_RETURN() || $event->key_sym() == SDLK_KP_ENTER()) {
  683.                         highlight_option('ok');
  684.                         $app->delay(200);
  685.                         delete_levelset();
  686.                     } elsif ($event->key_sym() == SDLK_ESCAPE()) {
  687.                         highlight_option('cancel');
  688.                         $app->delay(200);
  689.                         remove_dialog();
  690.                     } elsif ($event->key_sym() == SDLK_DOWN()) {
  691.                         display_levelset_list_browser($FBLE::list_browser_file_start_offset + 1, $FBLE::list_browser_highlight_offset + 1);
  692.                     } elsif ($event->key_sym() == SDLK_UP()) {
  693.                         display_levelset_list_browser($FBLE::list_browser_file_start_offset - 1, $FBLE::list_browser_highlight_offset - 1);
  694.                     }
  695.                 } elsif ($displaying_dialog eq 'ls_deleted_current') {
  696.                     if ($event->key_sym() == SDLK_RETURN() || $event->key_sym() == SDLK_KP_ENTER() ) {
  697.                         highlight_option('ok');
  698.                         $app->delay(200);
  699.                         remove_dialog();
  700.                         create_open_levelset_dialog_ok_only();
  701.                     } elsif ($event->key_sym() == SDLK_ESCAPE()) {
  702.                         highlight_option('cancel');
  703.                         $app->delay(200);
  704.                         $levelset_name = 'default-levelset';
  705.                         %bubble_hash = read_file($levelset_name);
  706.                         $curr_level = 1;
  707.                         $displaying_dialog = '';
  708.                         remove_dialog();
  709.                         load_level();
  710.                         print_levelset_name();
  711.                         $deleted_current_levelset = 0;
  712.                     }
  713.                 } elsif ($displaying_dialog eq 'ls_nothing_to_delete') {
  714.                     if ($event->key_sym() == SDLK_RETURN() || $event->key_sym() == SDLK_KP_ENTER()) {
  715.                         highlight_option('ok_right');
  716.                         $app->delay(200);
  717.                         $displaying_dialog = '';
  718.                         remove_dialog();
  719.                     }
  720.                 } elsif ($displaying_dialog eq 'ls_save_changes') {
  721.                     if ($event->key_sym() == SDLK_RETURN() || $event->key_sym() == SDLK_KP_ENTER() ) {
  722.                         highlight_option('ok');
  723.                         $app->delay(200);
  724.                         save_file();
  725.                         $displaying_dialog = '';
  726.                         eval($modified_levelset_action);
  727.                     } elsif ($event->key_sym() == SDLK_ESCAPE()) {
  728.                         highlight_option('cancel');
  729.                         $app->delay(200);
  730.                         $displaying_dialog = '';
  731.                         eval($modified_levelset_action);
  732.                     }
  733.                 }
  734.         
  735.                 $app->flip;
  736.  
  737.             } elsif ($event->type == SDL_QUIT) {
  738.                 if ($displaying_dialog eq '') {
  739.                     if ($modified_levelset == 1) {
  740.                         $modified_levelset_action = '$modified_levelset_action = "return 1"';
  741.                         create_save_changes_dialog();
  742.                         $app->flip;
  743.                     } else {
  744.                         return 1;
  745.                     }
  746.                 }
  747.             }
  748.  
  749.             if ($modified_levelset_action =~ /return (\S*)/ && $displaying_dialog ne 'ls_save_changes') {
  750.                 my $return_value = $1;
  751.                 $displaying_dialog = '';
  752.                 $modified_levelset_action = '';
  753.                 return $1;
  754.             }
  755.             
  756.         } else { 
  757.             $app->delay(1);
  758.         }
  759.  
  760.     }
  761. }
  762.  
  763.  
  764. #- ----------- dialogs -----------------------------------------
  765.  
  766. # subroutine to get the rect where the promt will go
  767. sub get_dialog_rect {
  768.     SDL::Rect->new(-x => $background->width/2 - $surface_dialog->width/2,
  769.            '-y' => $background->height/2 - $surface_dialog->height/2,
  770.            -width => $surface_dialog->width, -height => $surface_dialog->height);
  771. }
  772.  
  773. sub create_dialog_base {
  774.     my ($title_text) = @_;
  775.  
  776.     unhighlight_option();
  777.     $surface_dialog = SDL::Surface->new(-name => "$FPATH/gfx/menu/void_panel.png");
  778.  
  779.     $rect{dialog} = SDL::Rect->new(-x => 0, '-y' => 0, -width => $surface_dialog->width, -height => $surface_dialog->height);
  780.     $rect{middle} = get_dialog_rect();
  781.     
  782.     $surface_dialog->blit($rect{dialog}, $app, $rect{middle});
  783.     
  784.     $app->print($rect{middle}->x + $rect{middle}->width/2 - 12 * length($title_text)/2, $rect{middle}->y + 5, uc($title_text));
  785. }
  786.  
  787. # sub to create a blank dialog on the screen
  788. sub create_dialog {
  789.     my ($title_text) = @_;
  790.     create_dialog_base($title_text);
  791.     print_cancel_text(0);
  792.     print_ok_text(0);
  793.  
  794. }
  795.  
  796. sub remove_dialog {
  797.     $rect{middle} = SDL::Rect->new(-x => $background->width/2 - $surface_dialog->width/2,
  798.                    '-y' => $background->height/2 - $surface_dialog->height/2,
  799.                    -width => $surface_dialog->width, -height => $surface_dialog->height);
  800.     $background->blit($rect{middle}, $app, $rect{middle});
  801.     $app->update;
  802.  
  803.     # update the screen
  804.     load_level();
  805.     $displaying_dialog = '';
  806. }
  807.  
  808. # subroutine to ask the user what to do if they delete the current levelset
  809. sub create_deleted_current_levelset_dialog {
  810.     $displaying_dialog = 'ls_deleted_current';
  811.     $deleted_current_levelset = 1; 
  812.     create_dialog('DELETED CURRENT LEVELSET');
  813.     $rect{middle} = get_dialog_rect();
  814.     $app->print($rect{middle}->x + 25, $rect{middle}->y + 15 + $WOOD_PLANK_HEIGHT, "PRESS \"OK\" TO CHOOSE");
  815.     $app->print($rect{middle}->x + 25, $rect{middle}->y + 35 + $WOOD_PLANK_HEIGHT, "ANOTHER LEVELSET TO OPEN");
  816.     $app->print($rect{middle}->x + 25, $rect{middle}->y + 3 * $WOOD_PLANK_HEIGHT, "PRESS \"CANCEL\" TO OPEN");
  817.     $app->print($rect{middle}->x + 25, $rect{middle}->y + 25 + 3* $WOOD_PLANK_HEIGHT, "THE DEFAULT LEVELSET");
  818.  
  819. }
  820.  
  821. # subroutine to create a delete levelset dialog
  822. sub create_delete_levelset_dialog {
  823.     if (all($FBLEVELS) > 1) {
  824.         $displaying_dialog = 'ls_delete';
  825.         create_dialog('SELECT LEVELSET TO DELETE');
  826.         $list_browser_highlight_offset = -1;
  827.         $list_browser_file_start_offset = -1; 
  828.         display_levelset_list_browser(0, 0);
  829.     } else {
  830.         $displaying_dialog = 'ls_nothing_to_delete';
  831.         create_ok_dialog('NO LEVELSET TO DELETE');
  832.         $rect{middle} = get_dialog_rect();
  833.         $app->print($rect{middle}->x + 50, $rect{middle}->y + 30 + $WOOD_PLANK_HEIGHT, "THERE ARE NO CUSTOM"); 
  834.         $app->print($rect{middle}->x + 50, $rect{middle}->y + 55 + $WOOD_PLANK_HEIGHT, "LEVELSETS TO DELETE.");
  835.         $app->print($rect{middle}->x + 40, $rect{middle}->y + 125 + $WOOD_PLANK_HEIGHT, "PRESS \"OK\" TO CONTINUE");
  836.     }
  837. }
  838.  
  839. # subroutine to create a new levelset dialog. This dialog asks for the name of the new levelset
  840. sub create_new_levelset_dialog {
  841.     if ($modified_levelset == 1) {
  842.         $modified_levelset_action = 'create_new_levelset_dialog_ok_only()';
  843.         create_save_changes_dialog();
  844.     } else {
  845.         $displaying_dialog = 'ls_new';
  846.         # create the blank dialog with the title of "enter new levelset name"
  847.         create_dialog('ENTER NEW LEVELSET NAME');
  848.         # inialize the new levelset name
  849.         $new_ls_name_text = '';
  850.     }
  851.  
  852. }
  853.  
  854. sub create_new_levelset_dialog_ok_only {
  855.     $displaying_dialog = 'ls_new_ok_only';
  856.     create_ok_dialog('ENTER NEW LEVELSET NAME');
  857.     $new_ls_name_text = '';
  858. }
  859.  
  860. # sub to create a blank dialog on the screen
  861. sub create_ok_dialog {
  862.     my ($title_text) = @_;
  863.     create_dialog_base($title_text);
  864.     print_ok_right_text(0);
  865.  
  866. }
  867.  
  868. sub create_open_levelset_dialog {
  869.     if ($modified_levelset == 1) {
  870.         $modified_levelset_action = 'create_open_levelset_dialog_ok_only()';
  871.         create_save_changes_dialog();
  872.     } else {
  873.         $displaying_dialog = 'ls_open';
  874.         create_dialog('SELECT LEVELSET TO OPEN');
  875.         $list_browser_highlight_offset = -1;
  876.         $list_browser_file_start_offset = -1; 
  877.         display_levelset_list_browser(0,0);
  878.     }
  879. }
  880.  
  881. sub create_open_levelset_dialog_ok_only {
  882.  
  883.     $displaying_dialog = 'ls_open_ok_only';
  884.     create_ok_dialog('SELECT LEVELSET TO OPEN');
  885.     $list_browser_highlight_offset = -1;
  886.     $list_browser_file_start_offset = -1; 
  887.     display_levelset_list_browser(0,0);
  888.  
  889. }
  890.  
  891. sub iter_rowscols(&) {
  892.     my ($f) = @_;
  893.     local ($::row, $::col);
  894.     foreach $::row (0 .. $NUM_ROWS - 1) {
  895.     foreach $::col (0 .. ($POS_1P{p1}{right_limit}-$POS_1P{p1}{left_limit})/$BUBBLE_SIZE - 1 - odd($::row)) {
  896.         &$f;
  897.     }
  898.     }
  899. }
  900.  
  901. sub save_file {
  902.     my @contents;
  903.     foreach my $lev (1 .. keys %bubble_hash) {
  904.     iter_rowscols {
  905.         if ($::col == 0) {
  906.         ($lev == 1 && $::row == 0) or push @contents, "\n";
  907.         odd($::row) and push @contents, "  ";
  908.         }
  909.         push @contents, "$bubble_hash{$lev}{$::col}{$::row}";
  910.         $::col+odd($::row) < 7 and push @contents, "   ";
  911.         };
  912.     push @contents, "\n";
  913.     }
  914.     output("$FBLEVELS/$levelset_name", @contents);
  915.     $modified_levelset = 0;
  916. }
  917.  
  918. sub create_play_levelset_dialog {
  919.     $displaying_dialog = 'ls_play';
  920.     create_ok_dialog('SELECT LEVELSET TO PLAY');
  921.     $list_browser_highlight_offset = -1;
  922.     $list_browser_file_start_offset = -1; 
  923.     display_levelset_list_browser(0, 0);
  924. }
  925.  
  926. sub create_save_changes_dialog {
  927.     $modified_levelset = 0;
  928.     #reset the modified levelset flag
  929.     $displaying_dialog = 'ls_save_changes';
  930.     create_dialog('SAVE CHANGES?');
  931.  
  932.     $rect{middle} = get_dialog_rect();
  933.  
  934.     # write out the instructions
  935.     $app->print($rect{middle}->x + 25, $rect{middle}->y + $WOOD_PLANK_HEIGHT, 'THERE ARE UNSAVED CHANGES');
  936.     $app->print($rect{middle}->x + 22, $rect{middle}->y + 35 + $WOOD_PLANK_HEIGHT, "PRESS \"OK\" TO SAVE");
  937.     $app->print($rect{middle}->x + 22, $rect{middle}->y + 55 + $WOOD_PLANK_HEIGHT, "CHANGES AND CONTINUE");
  938.     $app->print($rect{middle}->x + 22, $rect{middle}->y + 95 + $WOOD_PLANK_HEIGHT, "PRESS \"CANCEL\" TO CONTINUE");
  939.     $app->print($rect{middle}->x + 22, $rect{middle}->y + 115 + $WOOD_PLANK_HEIGHT, "WITHOUT SAVING");
  940. }
  941.  
  942. # subroutine to display to the user a list of levelsets and allow them
  943. # to browse through and select one of them.
  944. sub display_levelset_list_browser {
  945.     my ($file_start_offset, $file_highlight_offset) = @_;
  946.     my ($surf_file_list_background, $surf_purple_highlight, $surf_scroll_list_background, $cnt);
  947.  
  948.     my @levelsets = get_levelset_list();
  949.     my $do_scroll = $file_start_offset != $list_browser_file_start_offset;
  950.  
  951.     if ($file_highlight_offset > @levelsets - 1
  952.     || $file_highlight_offset == $list_browser_highlight_offset && !$do_scroll) {
  953.         # this is the case where the user either clicks on the same
  954.         # file that is already selected, or clicks in the file box, but
  955.         # not on a file (for example, the user click on the second
  956.         # file section when only one file is displayed
  957.         return;
  958.     }
  959.  
  960.     # we can display 4 files. If the offset makes us print less than 1, ignore it
  961.     # also, make sure we don't let an offset of less than 0 go through
  962.     if ($file_start_offset < @levelsets && $file_start_offset >= 0) {
  963.         # save the current file offset
  964.         $list_browser_file_start_offset = $file_start_offset;
  965.     } else {
  966.         # we don't need to draw anything. just exit
  967.         return;
  968.     }
  969.  
  970.     $rect{middle} = get_dialog_rect();
  971.  
  972.     $surf_file_list_background = SDL::Surface->new(-name => "$FPATH/gfx/file_list_background.png");
  973.  
  974.     $rect{list_box_src} = SDL::Rect->new(-width => $surf_file_list_background->width,
  975.                      -height => 3 * $WOOD_PLANK_HEIGHT);
  976.  
  977.     $rect{dialog_file_list} = SDL::Rect->new(-x => $rect{middle}->x + 9, '-y' => $rect{middle}->y + $WOOD_PLANK_HEIGHT + 37,
  978.                          -width => $rect{list_box_src}->width, -height => $rect{list_box_src}->height);
  979.  
  980.     $surf_purple_highlight = SDL::Surface->new(-name => "$FPATH/gfx/purple_hover.gif");
  981.  
  982.     $rect{purple_highlight_src} = SDL::Rect->new(-width => $surf_purple_highlight->width,
  983.                          -height => $surf_purple_highlight->height);
  984.  
  985.     # we only want to draw the arrows and background here once, when we first get launched
  986.     if ($list_browser_highlight_offset == -1) {
  987.         $surf_scroll_list_background = SDL::Surface->new(-name => "$FPATH/gfx/scroll_list_background.png");
  988.  
  989.         $rect{scroll_list_background_src} = SDL::Rect->new(-width => $surf_scroll_list_background,
  990.                                -height => 3 * $WOOD_PLANK_HEIGHT);
  991.     
  992.         $rect{scroll_list_background_dest} = SDL::Rect->new(-x => $rect{dialog_file_list}->x + $rect{dialog_file_list}->width,
  993.                                 '-y' => $rect{dialog_file_list}->y,
  994.                                 -width => $rect{scroll_list_background_src}->width,
  995.                                 -height => $rect{scroll_list_background_src}->height);
  996.  
  997.         $surf_scroll_list_background->blit($rect{scroll_list_background_src}, $app, $rect{scroll_list_background_dest});
  998.         $app->update($rect{scroll_list_background_dest});
  999.     
  1000.         print_dialog_list_arrow_down(0);
  1001.         print_dialog_list_arrow_up(0);
  1002.         my $surface_loading = SDL::Surface->new(-name => "$FPATH/gfx/loadingpreviews.png");
  1003.  
  1004.         $surface_loading->blit(SDL::Rect->new(-width => $surface_loading->width, -height => $surface_loading->height),
  1005.                    $app,
  1006.                    SDL::Rect->new(-x => $rect{scroll_list_background_dest}->x + $rect{scroll_list_background_dest}->width + 7,
  1007.                           '-y' =>  $rect{scroll_list_background_dest}->y + 20,
  1008.                           -width => $surface_loading->width, -height => $surface_loading->width));
  1009.     }
  1010.  
  1011.     if ($do_scroll == 1) {
  1012.         $surf_file_list_background->blit($rect{list_box_src}, $app, $rect{dialog_file_list});
  1013.         $app->update($rect{dialog_file_list});
  1014.  
  1015.         for ($cnt = $file_start_offset; $cnt < $file_start_offset + 4; $cnt++) {
  1016.             if ($file_highlight_offset == $cnt) {
  1017.                 $rect{purple_highlight_dest} = SDL::Rect->new(
  1018.              -x => $rect{middle}->x + 12,
  1019.              '-y' => $rect{dialog_file_list}->y + 10 + 25 * ($cnt - $file_start_offset),
  1020.              -width => $surf_purple_highlight->width, -height => $surf_purple_highlight->height);
  1021.  
  1022.                 $surf_purple_highlight->blit($rect{purple_highlight_src}, $app, $rect{purple_highlight_dest});
  1023.                 $app->update($rect{purple_highlight_dest});
  1024.             }
  1025.             $app->print($rect{middle}->x + 19, $rect{dialog_file_list}->y + 8 + 25 * ($cnt - $file_start_offset),
  1026.             uc($levelsets[$cnt])); 
  1027.         }
  1028.     } else {
  1029.         # erase the old highlight 
  1030.         $rect{old_highlight} = SDL::Rect->new(
  1031.             -x => $rect{middle}->x + 12,
  1032.             '-y' => $rect{dialog_file_list}->y + 10 + 25 * ($list_browser_highlight_offset - $list_browser_file_start_offset), 
  1033.             -width => $surf_purple_highlight->width, -height => $surf_purple_highlight->height);
  1034.         
  1035.         $rect{erase_highlight} = SDL::Rect->new(
  1036.             -x => $rect{old_highlight}->x - $rect{dialog_file_list}->x,
  1037.         '-y' => $rect{old_highlight}->y - $rect{dialog_file_list}->y,
  1038.         -width => $surf_purple_highlight->width, -height => $surf_purple_highlight->height);
  1039.  
  1040.         # it is possible that the highlighed dude is off the screen. In this case, do not
  1041.         # call the blit, bacause there is no visible highlight to remove
  1042.         if ($list_browser_highlight_offset >= $list_browser_file_start_offset && $list_browser_highlight_offset <= $list_browser_file_start_offset + 3) { 
  1043.             $surf_file_list_background->blit($rect{erase_highlight}, $app, $rect{old_highlight});
  1044.             $app->update($rect{old_highlight});
  1045.             # draw the text of the old highligted dude
  1046.             $app->print($rect{middle}->x + 19,
  1047.             $rect{dialog_file_list}->y + 8 + 25 * ($list_browser_highlight_offset - $list_browser_file_start_offset),
  1048.             uc($levelsets[$list_browser_highlight_offset])); 
  1049.         }
  1050.  
  1051.         # draw the highlight
  1052.         $rect{purple_highlight_dest} = SDL::Rect->new(
  1053.             -x => $rect{middle}->x + 12,
  1054.             '-y' => $rect{dialog_file_list}->y + 10 + 25 * ($file_highlight_offset - $file_start_offset), 
  1055.             -width => $surf_purple_highlight->width,
  1056.             -height => $surf_purple_highlight->height);
  1057.         $surf_purple_highlight->blit($rect{purple_highlight_src}, $app, $rect{purple_highlight_dest});
  1058.         $app->update($rect{purple_highlight_dest});
  1059.         $app->print($rect{middle}->x + 19,
  1060.             $rect{dialog_file_list}->y + 8 + 25 * ($file_highlight_offset - $file_start_offset),
  1061.             uc($levelsets[$file_highlight_offset])); 
  1062.     }
  1063.  
  1064.     # set the unhighlight so the app thinks nothings highlighted and will highlight 
  1065.     # the correct item in the next highlight function call
  1066.     unhighlight_option();
  1067.  
  1068.     $app->update($rect{middle});
  1069.  
  1070.     if ($file_highlight_offset != $list_browser_highlight_offset) {
  1071.         if ($list_browser_highlight_offset == -1) {
  1072.             $list_browser_highlight_offset = 0;
  1073.  
  1074.         @file_browser_screenshots = ();
  1075.         my @levelset_list = get_levelset_list();
  1076.         foreach my $levelset (@levelset_list) {
  1077.         my $surface_screenshot = SDL::Surface->new(-name => "$FPATH/gfx/level_editor.png");
  1078.         load_level($surface_screenshot, read_file($levelset));
  1079.         $file_browser_screenshots[@file_browser_screenshots] = $surface_screenshot; 
  1080.         }
  1081.         }
  1082.         $list_browser_highlight_offset = $file_highlight_offset;
  1083.         display_levelset_screenshot();
  1084.     } else {
  1085.         $list_browser_highlight_offset = $file_highlight_offset;
  1086.     }
  1087.  
  1088.     $app->flip;
  1089. }
  1090.  
  1091. # display a scrrenshot (1/4 size) of the first level in a levelset on the current dialog
  1092. sub display_levelset_screenshot {
  1093.     $rect{middle} = get_dialog_rect();
  1094.     $rect{screenshot} = SDL::Rect->new(-x => $POS_1P{p1}{left_limit} - 40, '-y' => 0, 
  1095.                        -width => $POS_1P{p1}{right_limit} - $POS_1P{p1}{left_limit} + 80,
  1096.                        -height => $POS_1P{bottom_limit} - $POS_1P{init_top_limit} + 190);
  1097.     if ($SDL::VERSION >= 1.20) {
  1098.         fb_c_stuff::shrink($$app,
  1099.                 $file_browser_screenshots[$list_browser_highlight_offset]->display_format,
  1100.                 $rect{middle}->x + $rect{middle}->width - $rect{screenshot}->width/4 - 12,
  1101.                 $rect{middle}->y + $rect{middle}->height/2 - $rect{screenshot}->height/8 - 3,
  1102.                 $rect{screenshot}{-rect}, 4);
  1103.     }
  1104.     else {
  1105.         fb_c_stuff::shrink($app->{-surface},
  1106.                 $file_browser_screenshots[$list_browser_highlight_offset]->display_format()->{-surface},
  1107.                 $rect{middle}->x + $rect{middle}->width - $rect{screenshot}->width/4 - 12,
  1108.                 $rect{middle}->y + $rect{middle}->height/2 - $rect{screenshot}->height/8 - 3,
  1109.                 $rect{screenshot}{-rect}, 4);
  1110.     }
  1111. }
  1112.  
  1113.  
  1114. #- ----------- levels and levelsets operations ------------------------------------------
  1115.  
  1116. # subroutine load_level
  1117. sub load_level {
  1118.     my ($surface_tmp, %b) = @_;
  1119.     my $curr_lvl;
  1120.  
  1121.     if ($surface_tmp) {
  1122.         $curr_lvl = 1;
  1123.     } else {
  1124.         $curr_lvl = $curr_level;
  1125.         %b = %bubble_hash;
  1126.         clear_level();
  1127.         print_level_nb();
  1128.     }
  1129.  
  1130.     iter_rowscols {
  1131.     my $bub = \$b{$curr_lvl}{$::col}{$::row};
  1132.     defined($$bub) or $$bub = '-';  #- sanitize
  1133.     if ($$bub ne '-') {
  1134.         draw_bubble($$bub + 1,
  1135.             $::col * $BUBBLE_SIZE + $POS_1P{p1}{left_limit} + odd($::row)*$BUBBLE_SIZE/2,
  1136.             $::row * $ROW_SIZE + $POS_1P{init_top_limit},
  1137.             $ALPHA_BUBBLE_NO, $surface_tmp, undef, 1);
  1138.     }
  1139.     };
  1140.  
  1141.     $app->flip;
  1142. }
  1143.  
  1144. # subroutine to clear level off the screen
  1145. sub clear_level {
  1146.     $rect{clear} = SDL::Rect->new(-width => $POS_1P{p1}{right_limit} - $POS_1P{p1}{left_limit},
  1147.                   -height => $POS_1P{bottom_limit} - $POS_1P{init_top_limit} + $BUBBLE_SIZE,
  1148.                   -x => $POS_1P{p1}{left_limit},
  1149.                   '-y' => $POS_1P{init_top_limit});
  1150.     
  1151.     $background->blit($rect{clear}, $app, $rect{clear});
  1152.     $app->update;
  1153. }
  1154.  
  1155. sub delete_level {
  1156.  
  1157.     delete $bubble_hash{$curr_level};
  1158.  
  1159.     if ($curr_level - 1 == keys %bubble_hash) {
  1160.         $curr_level--;
  1161.         if ($curr_level == 0) {
  1162.             append_level();
  1163.         }
  1164.     } else {
  1165.         foreach my $lev ($curr_level .. keys %bubble_hash) {
  1166.             $bubble_hash{$lev} = $bubble_hash{$lev + 1};
  1167.         }
  1168.  
  1169.         delete $bubble_hash{keys %bubble_hash};
  1170.     }   
  1171.  
  1172.     $modified_levelset = 1;
  1173. }
  1174.  
  1175. # subroutine to actually create a new levelset
  1176. sub create_new_levelset {
  1177.  
  1178.     $levelset_name = lc($new_ls_name_text);
  1179.  
  1180.     %bubble_hash = ();
  1181.     $curr_level = 0;
  1182.     append_level();
  1183.  
  1184.     print_levelset_name();
  1185.     load_level();
  1186.     $modified_levelset = 0;
  1187. }
  1188.  
  1189. # subroutine to delete a levelset
  1190. sub delete_levelset {
  1191.  
  1192.     my @levelsets = get_levelset_list();
  1193.     my $lvs_name = $levelsets[$list_browser_highlight_offset];
  1194.     unlink "$FBLEVELS/$lvs_name" or die "Can't remove $FBLEVELS/$lvs_name\n";
  1195.     remove_dialog();
  1196.  
  1197.     $levelset_name eq $lvs_name and create_deleted_current_levelset_dialog();
  1198. }
  1199.  
  1200. # this subroutine is mostly copied from frozen-bubble
  1201. sub read_file {
  1202.     my ($file_name) = @_;
  1203.  
  1204.     my $row = 0;
  1205.     my $lev_number = 1;
  1206.     my %tmp_hash;
  1207.     foreach my $line (cat_("$FBLEVELS/$file_name")) {
  1208.         if ($line !~ /\S/) {
  1209.             if ($row) {
  1210.                 $lev_number++;
  1211.                 $row = 0;
  1212.             }
  1213.         } else {
  1214.             my $col = 0;
  1215.             foreach (split ' ', $line) {
  1216.                 $tmp_hash{$lev_number}{$col}{$row} = $_;
  1217.                 $col++;
  1218.             }
  1219.             $row++;
  1220.         }
  1221.     }
  1222.  
  1223.     return %tmp_hash;
  1224.  
  1225. }
  1226.  
  1227. # subroutine to open the levelset
  1228. sub open_levelset {
  1229.     my (@levelsets);
  1230.     # reset the deleted_current_levelset flag in case
  1231.     # we were in that situation
  1232.  
  1233.     $deleted_current_levelset = 0; 
  1234.     @levelsets = get_levelset_list();
  1235.  
  1236.     $levelset_name = $levelsets[$list_browser_highlight_offset];
  1237.     %bubble_hash = read_file($levelset_name);
  1238.     print_levelset_name();
  1239.     $curr_level = 1;
  1240.     remove_dialog();
  1241.     $modified_levelset = 0;
  1242. }
  1243.  
  1244.  
  1245. #- ----------- navigation in a levelset ------------------------------------------
  1246.  
  1247. sub prev_level {
  1248.     $curr_level > 1 and $curr_level--;
  1249.     load_level();
  1250. }
  1251.  
  1252. sub next_level {
  1253.     $curr_level < keys %bubble_hash and $curr_level++;
  1254.     load_level();
  1255. }
  1256.  
  1257. sub first_level {
  1258.     $curr_level = 1;
  1259.     load_level();
  1260. }
  1261.  
  1262. sub last_level {
  1263.     $curr_level = keys %bubble_hash;
  1264.     load_level();
  1265. }
  1266.  
  1267. sub jump_to_level {
  1268.     my ($n) = @_;
  1269.     if ($n >= 1 && $n <= keys %bubble_hash) {
  1270.     $curr_level = $_[0];
  1271.     load_level();
  1272.     }
  1273. }
  1274.  
  1275. sub insert_level {
  1276.     for (my $lev = 1 + keys %bubble_hash; $lev > $curr_level; $lev--) {
  1277.         $bubble_hash{$lev} = $bubble_hash{$lev - 1};
  1278.     }
  1279.  
  1280.     delete $bubble_hash{$curr_level};
  1281.     
  1282.     # initialize our new level
  1283.     iter_rowscols { $bubble_hash{$curr_level}{$::col}{$::row} = '-' };
  1284.  
  1285.     load_level();
  1286.     $modified_levelset = 1;
  1287. }
  1288.  
  1289. sub append_level {
  1290.     $curr_level++;
  1291.     insert_level();
  1292. }
  1293.  
  1294. sub move_level_left {
  1295.     $curr_level > 1 or return;
  1296.     ($bubble_hash{$curr_level-1}, $bubble_hash{$curr_level}) = ($bubble_hash{$curr_level}, $bubble_hash{$curr_level-1});
  1297.     $curr_level--;
  1298.     load_level();
  1299.     $modified_levelset = 1;
  1300. }
  1301.  
  1302. sub move_level_right {
  1303.     $curr_level < keys(%bubble_hash) or return;
  1304.     ($bubble_hash{$curr_level+1}, $bubble_hash{$curr_level}) = ($bubble_hash{$curr_level}, $bubble_hash{$curr_level+1});
  1305.     $curr_level++;
  1306.     load_level();
  1307.     $modified_levelset = 1;
  1308. }
  1309.  
  1310.  
  1311. #- ----------- printing stuff ------------------------------------------
  1312.  
  1313. sub print_cancel_text {
  1314.     my ($do_highlight) = @_;
  1315.  
  1316.     if ($displaying_dialog ne '') {
  1317.         $rect{middle} = get_dialog_rect();
  1318.  
  1319.         $rect{cancel_src} = SDL::Rect->new(-x => $rect{middle}->width - $rect{option_highlight}->width, 
  1320.                        '-y' => 6 * $WOOD_PLANK_HEIGHT - 4,
  1321.                        -width => $rect{middle}->width/2, -height => $WOOD_PLANK_HEIGHT);
  1322.     
  1323.         $rect{cancel} = SDL::Rect->new(-x => $rect{middle}->x + $rect{middle}->width - $rect{option_highlight}->width,
  1324.                        '-y' => $rect{middle}->y + 6 * $WOOD_PLANK_HEIGHT - 4,
  1325.                        -width => $rect{middle}->width/2, -height => $WOOD_PLANK_HEIGHT);
  1326.  
  1327.         $surface_dialog->blit($rect{cancel_src}, $app, $rect{cancel});
  1328.         $app->update($rect{cancel});
  1329.     
  1330.         $app->print($rect{middle}->x + $rect{middle}->width - 120, $rect{middle}->y + 6 * $WOOD_PLANK_HEIGHT, 'CANCEL');
  1331.         if ($do_highlight) {
  1332.             $highlight->blit($rect{option_highlight}, $app, $rect{cancel});
  1333.             $app->update($rect{cancel});
  1334.         }
  1335.     }
  1336. }
  1337.  
  1338. sub print_dialog_list_arrow {
  1339.     my ($do_highlight, $type) = @_;
  1340.  
  1341.     $rect{middle} = get_dialog_rect();
  1342.     
  1343.     my $surf_list_arrow = SDL::Surface->new(-name => "$FPATH/gfx/list_arrow_$type.png");
  1344.     $rect{list_arrow_src} = SDL::Rect->new(-width => $surf_list_arrow->width, -height => $surf_list_arrow->height);
  1345.     $rect{list_arrow_dest} = SDL::Rect->new(
  1346.             '-x' => $rect{middle}->x + 4 * $rect{middle}->width/6 + 2,
  1347.             '-y' => $type eq 'up' ? $rect{dialog_file_list}->y + 2
  1348.                                   : $rect{dialog_file_list}->y + $rect{dialog_file_list}->height - $surf_list_arrow->height - 2,
  1349.             -width => $surf_list_arrow->width, -height => $surf_list_arrow->height);
  1350.  
  1351.     my $surf_scroll_list_background = SDL::Surface->new(-name => "$FPATH/gfx/scroll_list_background.png");
  1352.     $rect{erase_arrow} = SDL::Rect->new('-x' => $rect{list_arrow_dest}->x - $rect{scroll_list_background_dest}->x,
  1353.                     '-y' => $rect{list_arrow_dest}->y - $rect{scroll_list_background_dest}->y,
  1354.                     -width => $surf_list_arrow->width, -height => $surf_list_arrow->height);
  1355.  
  1356.     $surf_scroll_list_background->blit($rect{erase_arrow}, $app, $rect{list_arrow_dest});
  1357.     $app->update($rect{list_arrow_dest});
  1358.  
  1359.     $surf_list_arrow->blit($rect{list_arrow_src}, $app, $rect{list_arrow_dest});
  1360.     $app->update($rect{list_arrow_dest});
  1361.  
  1362.     if ($do_highlight) {
  1363.         $highlight->blit($rect{list_arrow_src}, $app, $rect{list_arrow_dest});
  1364.         $app->update($rect{list_arrow_dest});
  1365.     }
  1366. }
  1367.  
  1368. sub print_dialog_list_arrow_down {
  1369.     my ($do_highlight) = @_;
  1370.     print_dialog_list_arrow($do_highlight, 'down');
  1371. }
  1372.  
  1373. sub print_dialog_list_arrow_up {
  1374.     my ($do_highlight) = @_;
  1375.     print_dialog_list_arrow($do_highlight, 'up');
  1376. }
  1377.  
  1378. # subroutine to print out the levelset name at the top of the screen
  1379. sub print_levelset_name {
  1380.     $rect{ls_name_erase} = SDL::Rect->new(-x => 195, '-y' => 0, -width => 445-195, -height => 35);
  1381.     $background->blit($rect{ls_name_erase}, $app, $rect{ls_name_erase});
  1382.     $app->update;
  1383.     $app->print(($background->width - SDL_TEXTWIDTH(uc($levelset_name)))/2 - 6, 7, uc($levelset_name));
  1384. }
  1385.  
  1386. sub print_text_generic {
  1387.     my ($do_highlight, $name, $xpos, $ypos, $text) = @_;
  1388.  
  1389.     $background->blit($rect{$name}, $app, $rect{$name});
  1390.     $app->update($rect{$name});
  1391.  
  1392.     $app->print($xpos, $ypos, $text || uc($name));
  1393.     if ($do_highlight) {
  1394.         $highlight->blit($rect{option_highlight}, $app, $rect{$name});
  1395.         $app->update($rect{$name});
  1396.     }
  1397. }
  1398.     
  1399. sub print_first_text {
  1400.     print_text_generic($_[0], 'first', $WOOD_WIDTH/2, $rect{first}->y + 6);
  1401. }
  1402.  
  1403. sub print_last_text {
  1404.     print_text_generic($_[0], 'last', 20, $rect{last}->y + 6);
  1405. }
  1406.  
  1407. sub print_prev_text {
  1408.     print_text_generic($_[0], 'prev', $WOOD_WIDTH/2, $rect{prev}->y + 6);
  1409. }
  1410.  
  1411. sub print_next_text {
  1412.     print_text_generic($_[0], 'next', 20, $rect{next}->y + 6);
  1413. }
  1414.  
  1415. sub print_ls_delete_text {
  1416.     print_text_generic($_[0], 'ls_delete', $rect{ls_delete}->x + 12, $rect{ls_delete}->y + 6, 'DELETE');
  1417. }
  1418.  
  1419. sub print_ls_new_text {
  1420.     print_text_generic($_[0], 'ls_new', $rect{ls_new}->x + $WOOD_WIDTH/2, $rect{ls_new}->y + 6, 'NEW');
  1421. }
  1422.  
  1423. sub print_ls_open_text {
  1424.     print_text_generic($_[0], 'ls_open', $rect{ls_open}->x + 35, $rect{ls_open}->y + 6, 'OPEN');
  1425. }
  1426.  
  1427. sub print_ls_save_text {
  1428.     print_text_generic($_[0], 'ls_save', $rect{ls_save}->x + $WOOD_WIDTH/2, $rect{ls_save}->y + 6, 'SAVE');
  1429. }
  1430.  
  1431. sub print_lvl_append_text {
  1432.     print_text_generic($_[0], 'lvl_append', $rect{lvl_append}->x + 20, $rect{lvl_append}->y + 6, 'APPEND');
  1433. }
  1434.  
  1435. sub print_lvl_delete_text {
  1436.     print_text_generic($_[0], 'lvl_delete', $rect{lvl_delete}->x + $WOOD_WIDTH/2, $rect{lvl_delete}->y + 6, 'DELETE');
  1437. }
  1438.  
  1439. sub print_lvl_insert_text {
  1440.     print_text_generic($_[0], 'lvl_insert', $rect{lvl_insert}->x + $WOOD_WIDTH/2 - 5, $rect{lvl_insert}->y + 6, 'INSERT');
  1441. }
  1442.  
  1443. # filename is OK == not blank or pre-existing
  1444. sub is_ok_filename {
  1445.     length($new_ls_name_text) == 0 and return 0;
  1446.  
  1447.     lc($new_ls_name_text) eq lc($_) and return 0 foreach get_levelset_list();
  1448.  
  1449.     return 1;
  1450. }
  1451.  
  1452. # subroutine to get the letter pressed by the user on the keyboard
  1453. # this subroutine is taken from frozen-bubble code
  1454. sub keysym_to_char($) { 
  1455.     my ($key) = @_; 
  1456.     eval "$key eq SDLK_$_" and return uc($_) foreach @fbsyms::syms; 
  1457. }
  1458.  
  1459. # subroutine to print the name of the new levelset in the dialog
  1460. sub print_new_ls_name {
  1461.     my ($key) = @_;
  1462.     if ($key == SDLK_ESCAPE()) {
  1463.         if ($displaying_dialog eq 'ls_new') {
  1464.             highlight_option('cancel');
  1465.             $app->delay(200);
  1466.             remove_dialog(); 
  1467.         }
  1468.     } elsif (($key == SDLK_RETURN() || $key == SDLK_KP_ENTER()) && length($new_ls_name_text) > 0 && is_ok_filename() ) {
  1469.         if ($displaying_dialog eq 'ls_new') {
  1470.             highlight_option('ok');
  1471.         } elsif ($displaying_dialog eq 'ls_new_ok_only') {
  1472.             highlight_option('ok_right');
  1473.         }
  1474.         $app->delay(200);
  1475.         remove_dialog(); 
  1476.         create_new_levelset();
  1477.     } elsif ($key == SDLK_BACKSPACE()
  1478.          || (length($new_ls_name_text) < 14 && ($key == SDLK_KP_MINUS()
  1479.                             || $key >= SDLK_KP0() && $key <= SDLK_KP9()
  1480.                             || $key >= SDLK_a() && $key <= SDLK_z()
  1481.                             || $key == SDLK_MINUS()
  1482.                             || $key >= SDLK_0 && $key <= SDLK_9()))) {
  1483.         # first erase the previous words
  1484.         $rect{dialog_blank} = SDL::Rect->new('-y' => 2 * $WOOD_PLANK_HEIGHT,
  1485.                          -width => $surface_dialog->width,
  1486.                          -height => $surface_dialog->height - 3 * $WOOD_PLANK_HEIGHT);
  1487.         $rect{dialog_new} = SDL::Rect->new(-x => $background->width/2 - $surface_dialog->width/2, 
  1488.                        '-y' => $background->height/2 - $surface_dialog->height/2 + 2 * $WOOD_PLANK_HEIGHT, 
  1489.                        -width => $surface_dialog->width,
  1490.                        -height => $surface_dialog->height - 3*$WOOD_PLANK_HEIGHT);
  1491.         $surface_dialog->blit($rect{dialog_blank}, $app, $rect{dialog_new});
  1492.         $app->update;
  1493.         if ($key == SDLK_BACKSPACE()) {
  1494.             chop $new_ls_name_text;
  1495.         } elsif ($key == SDLK_MINUS() || $key == SDLK_KP_MINUS()) {
  1496.             $new_ls_name_text .= '-';
  1497.         } elsif ($key >= SDLK_KP0() && $key <= SDLK_KP9()) {
  1498.             my $kp_num;
  1499.         eval("SDLK_KP$_() eq $key") and $new_ls_name_text .= $_ foreach 0..9;
  1500.         } else {
  1501.             $new_ls_name_text .= keysym_to_char($key); 
  1502.         }
  1503.         $app->print($rect{dialog_new}->x + $rect{dialog_new}->width/2 - 12 * length($new_ls_name_text)/2, 210, $new_ls_name_text);
  1504.     }
  1505.  
  1506.     # if the filename is bad, unhighlight any option that is highlighted since
  1507.     # they can't do anything...
  1508.     is_ok_filename() == 0 and unhighlight_option();
  1509.  
  1510. }
  1511.  
  1512. # subroutine to print the ok text on the right side of the dialog
  1513. sub print_ok_right_text {
  1514.     my ($do_highlight) = @_;
  1515.     if ($displaying_dialog ne '') {
  1516.         $rect{middle} = get_dialog_rect();
  1517.  
  1518.         $rect{cancel_src} = SDL::Rect->new(-x => $rect{middle}->width - $rect{option_highlight}->width, 
  1519.                        '-y' => 6 * $WOOD_PLANK_HEIGHT - 4,
  1520.                        -width => $rect{middle}->width/2,
  1521.                        -height => $WOOD_PLANK_HEIGHT);
  1522.     
  1523.         $rect{cancel} = SDL::Rect->new(-x => $rect{middle}->x + $rect{middle}->width - $rect{option_highlight}->width,
  1524.                        '-y' => $rect{middle}->y + 6 * $WOOD_PLANK_HEIGHT - 4,
  1525.                        -width => $rect{middle}->width/2,
  1526.                        -height => $WOOD_PLANK_HEIGHT);
  1527.  
  1528.         $surface_dialog->blit($rect{cancel_src}, $app, $rect{cancel});
  1529.         $app->update($rect{cancel});
  1530.     
  1531.         $app->print($rect{middle}->x + $rect{middle}->width - 80, $rect{middle}->y + 6 * $WOOD_PLANK_HEIGHT, 'OK');
  1532.         if ($do_highlight) {
  1533.             $highlight->blit($rect{option_highlight}, $app, $rect{cancel});
  1534.             $app->update($rect{cancel});
  1535.         }
  1536.     }
  1537. }
  1538.  
  1539. sub print_ok_text {
  1540.     my ($do_highlight) = @_;
  1541.  
  1542.     if ($displaying_dialog ne '') {
  1543.         $rect{middle} = get_dialog_rect();
  1544.  
  1545.         $rect{ok_src} = SDL::Rect->new('-y' => 6 * $WOOD_PLANK_HEIGHT - 4, 
  1546.                        -width => $rect{middle}->width/2,
  1547.                        -height => $WOOD_PLANK_HEIGHT);
  1548.     
  1549.         $rect{ok} = SDL::Rect->new(-x => $rect{middle}->x,
  1550.                    '-y' => $rect{middle}->y + 6 * $WOOD_PLANK_HEIGHT - 4,
  1551.                    -width => $rect{middle}->width/2,
  1552.                    -height => $WOOD_PLANK_HEIGHT);
  1553.  
  1554.         $surface_dialog->blit($rect{ok_src}, $app, $rect{ok});
  1555.         $app->update($rect{ok});
  1556.     
  1557.         $app->print($rect{middle}->x + 60, $rect{middle}->y + 6 * $WOOD_PLANK_HEIGHT, 'OK');
  1558.         if ($do_highlight) {
  1559.             $highlight->blit($rect{option_highlight}, $app, $rect{ok});
  1560.             $app->update($rect{ok});
  1561.         }
  1562.     }
  1563. }
  1564.  
  1565. sub print_level_nb {
  1566.     my $level_sign_rect = SDL::Rect->new(-x => $POS_1P{p1}{scoresx} - 50, '-y' => $POS_1P{scoresy},
  1567.                      -width => 100, -height => 25);
  1568.  
  1569.     $background->blit($level_sign_rect, $app, $level_sign_rect);
  1570.     $app->update($level_sign_rect);
  1571.  
  1572.     my $text = "$curr_level/" . keys %bubble_hash;
  1573.  
  1574.     $app->print($POS_1P{p1}{scoresx} - 12 * length($text)/2, $POS_1P{scoresy}, $text);
  1575.  
  1576. }
  1577.  
  1578.  
  1579. #- ----------- initialization stuff ------------------------------------------
  1580.  
  1581. # subroutine to add specific bubble option
  1582. sub add_bubble_option {
  1583.     my ($bubble_id, $col, $row) = @_;
  1584.     draw_bubble($bubble_id, bubble_optionx($col), bubble_optiony($row), $ALPHA_BUBBLE_NO);
  1585. }
  1586.  
  1587. # subroutine to add the bubble options
  1588. sub add_bubble_options {
  1589.     my ($count, $col_count);
  1590.     # add my list of bubbles on the left
  1591.     $count = 0;
  1592.  
  1593.     while ($count < $NUM_BUBBLES_AVAIL) {
  1594.         $col_count = 0;
  1595.         while ($col_count < $BUBBLES_PER_ROW && $count < $NUM_BUBBLES_AVAIL) {
  1596.             add_bubble_option($count + 1, $col_count, floor($count/$BUBBLES_PER_ROW));
  1597.             $col_count++;
  1598.             $count++;
  1599.         }
  1600.     }
  1601.  
  1602.     if ($col_count >= $BUBBLES_PER_ROW) {
  1603.         $col_count = 0;
  1604.     }
  1605.  
  1606. }
  1607.  
  1608. # subroutine to add the erase option
  1609. sub add_erase_option {
  1610.     my $erase = SDL::Surface->new(-name => "$FPATH/gfx/balls/stick_effect_7.png");
  1611.     $erase->blit(NULL, $app, $rect{erase});
  1612.     $app->update($rect{erase});
  1613. }
  1614.  
  1615. # subroutine to do the initial setup
  1616. sub init_setup {
  1617.     my ($application_caller, $sdlapp) = @_;
  1618.  
  1619.     init_app($application_caller, $sdlapp);
  1620.  
  1621.     $background->blit(NULL, $app, $rect{background});
  1622.     $app->update($rect{background});
  1623.  
  1624.     add_bubble_options();
  1625.     add_erase_option();
  1626.  
  1627.     # set font
  1628.     $font = new SDL::Font("$FPATH/gfx/font.png");
  1629.     
  1630.     $app->print(5, $BUBBLE_WOOD_Y + 3, 'CHOOSE BUBBLE');
  1631.  
  1632.     # add navigation words
  1633.     $app->print(20, $NAV_WOOD_Y + 8,'NAVIGATION');
  1634.     print_prev_text(0);
  1635.     print_next_text(0);
  1636.     print_first_text(0);
  1637.     print_last_text(0);
  1638.  
  1639.     # add levelset words
  1640.     $app->print($RIGHT_WOOD_X + 30, $LEVELSET_WOOD_Y + 8, 'LEVELSET');
  1641.     print_ls_new_text(0);
  1642.     print_ls_open_text(0);
  1643.     print_ls_save_text(0);
  1644.     print_ls_delete_text(0);
  1645.  
  1646.     # add level words
  1647.     $app->print($RIGHT_WOOD_X + 45, $LEVEL_WOOD_Y + 8, 'LEVEL');
  1648.     print_lvl_insert_text(0);
  1649.     print_lvl_append_text(0);
  1650.     print_lvl_delete_text(0);
  1651.  
  1652.     # add initial bubble to draw
  1653.     change_color(1);
  1654.  
  1655.     $modified_levelset = 0;
  1656.     -d "$FBLEVELS" or mkdir "$FBLEVELS" or die "Can't create $FBLEVELS directory.\n";
  1657.     -f "$FBLEVELS/default-levelset" or cp_af("$FPATH/data/levels", "$FBLEVELS/default-levelset");
  1658.  
  1659.     %bubble_hash = read_file($levelset_name);
  1660.  
  1661.     # if inputted level is > the number of levels than reset current level to 1
  1662.     $curr_level > keys %bubble_hash and $curr_level = 1;
  1663.     load_level();
  1664.  
  1665.     SDL::WarpMouse(320, 240);
  1666.  
  1667.     print_levelset_name();
  1668.     $app->flip;
  1669.  
  1670.     $button_hold = 0;
  1671. }
  1672.  
  1673. # subroutine to initialize the application
  1674. sub init_app {
  1675.     my ($application_caller, $sdlapp) = @_;
  1676.     my @rcfile_data;
  1677.  
  1678.     $app = $sdlapp;
  1679.     # we only want to check to see if we're in full screen if we're
  1680.     # running as a stand alone app. If we're running embedded in the
  1681.     # game, we'll use whatever is already set up
  1682.     if ($application_caller eq 'stand-alone') {
  1683.     @rcfile_data = cat_("$FPATH/.fbrc");
  1684.  
  1685.         if ($command_line_fullscreen == 1) {
  1686.         $app->fullscreen;
  1687.         } elsif ($rcfile_data[0] eq "\$fullscreen = 1;\n") {
  1688.             $app->fullscreen;
  1689.         }
  1690.     } else {
  1691.         # we need to set the default levelset name to "default-levelset"
  1692.         $levelset_name = 'default-levelset';
  1693.         $curr_level = 1;
  1694.     }
  1695.  
  1696.     # background image
  1697.     $background = SDL::Surface->new(-name => "$FPATH/gfx/level_editor.png");
  1698.  
  1699.     my @allrects =
  1700.      ({ name => 'background', width => $background->width, height => $background->height },
  1701.       # bubble wood rectangle (without heading part)
  1702.       { name => 'bubble_wood', x => $LEFT_WOOD_X, 'y' => $BUBBLE_WOOD_Y + $WOOD_PLANK_HEIGHT,
  1703.     width => $WOOD_WIDTH, height => $WOOD_PLANK_HEIGHT * ($NUM_BUBBLES_AVAIL + 1)/$BUBBLES_PER_ROW },
  1704.       { name => 'erase', x => bubble_optionx($NUM_BUBBLES_AVAIL % $BUBBLES_PER_ROW),
  1705.     'y' => bubble_optiony(floor($NUM_BUBBLES_AVAIL / $BUBBLES_PER_ROW)),
  1706.     width => $BUBBLE_SIZE, height => $BUBBLE_SIZE },
  1707.  
  1708.       # navigation rectangles
  1709.       { name => 'prev', x => $LEFT_WOOD_X, 'y' => $NAV_WOOD_Y + $WOOD_PLANK_HEIGHT,
  1710.     width => $WOOD_WIDTH, height => $WOOD_PLANK_HEIGHT },
  1711.       { name => 'next', x => $LEFT_WOOD_X, 'y' => $NAV_WOOD_Y + 2 * $WOOD_PLANK_HEIGHT,
  1712.     width => $WOOD_WIDTH, height => $WOOD_PLANK_HEIGHT },
  1713.       { name => 'first', x => $LEFT_WOOD_X, 'y' => $NAV_WOOD_Y + 3 * $WOOD_PLANK_HEIGHT,
  1714.     width => $WOOD_WIDTH, height => $WOOD_PLANK_HEIGHT },
  1715.       { name => 'last', x => $LEFT_WOOD_X, 'y' => $NAV_WOOD_Y + 4 * $WOOD_PLANK_HEIGHT,
  1716.     width => $WOOD_WIDTH, height => $WOOD_PLANK_HEIGHT },
  1717.  
  1718.       # levelset rectangles
  1719.       { name => 'ls_new', x => $RIGHT_WOOD_X, 'y' => $LEVELSET_WOOD_Y + $WOOD_PLANK_HEIGHT,
  1720.     width => $WOOD_WIDTH, height => $WOOD_PLANK_HEIGHT },
  1721.       { name => 'ls_open', x => $RIGHT_WOOD_X, 'y' => $LEVELSET_WOOD_Y + 2 * $WOOD_PLANK_HEIGHT,
  1722.     width => $WOOD_WIDTH, height => $WOOD_PLANK_HEIGHT },
  1723.       { name => 'ls_save', x => $RIGHT_WOOD_X, 'y' => $LEVELSET_WOOD_Y + 3 * $WOOD_PLANK_HEIGHT,
  1724.     width => $WOOD_WIDTH, height => $WOOD_PLANK_HEIGHT },
  1725.       { name => 'ls_delete', x => $RIGHT_WOOD_X, 'y' => $LEVELSET_WOOD_Y + 4 * $WOOD_PLANK_HEIGHT,
  1726.     width => $WOOD_WIDTH, height => $WOOD_PLANK_HEIGHT },
  1727.  
  1728.       # level rectangles
  1729.       { name => 'lvl_insert', x => $RIGHT_WOOD_X, 'y' => $LEVEL_WOOD_Y + $WOOD_PLANK_HEIGHT,
  1730.     width => $WOOD_WIDTH, height => $WOOD_PLANK_HEIGHT },
  1731.       { name => 'lvl_append', x => $RIGHT_WOOD_X, 'y' => $LEVEL_WOOD_Y + 2 * $WOOD_PLANK_HEIGHT,
  1732.     width => $WOOD_WIDTH, height => $WOOD_PLANK_HEIGHT },
  1733.       { name => 'lvl_delete', x => $RIGHT_WOOD_X, 'y' => $LEVEL_WOOD_Y + 3 * $WOOD_PLANK_HEIGHT,
  1734.     width => $WOOD_WIDTH, height => $WOOD_PLANK_HEIGHT },
  1735.  
  1736.       { name => 'bubble_option_highlight', x => 0, 'y' => 0,
  1737.     width => $BUBBLE_SIZE, height => $BUBBLE_SIZE },
  1738.       { name => 'option_highlight', x => 0, 'y' => 0,
  1739.     width => $WOOD_WIDTH, height => $WOOD_PLANK_HEIGHT }
  1740.       );
  1741.  
  1742.     $rect{$_->{name}} = SDL::Rect->new(-width => $_->{width}, -height => $_->{height},
  1743.                        -x => $_->{x}, '-y' => $_->{'y'}) foreach @allrects;
  1744.  
  1745.     $highlight = SDL::Surface->new(-name => "$FPATH/gfx/hover.gif");
  1746.     $highlight->set_alpha(SDL_SRCALPHA, 0x44);
  1747. }
  1748.  
  1749.  
  1750. 1;
  1751.